package com.atguigu.juc.locksupport;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author yanglin
 * @create 2024-01-10 10:10 星期三
 * description:
 */
public class LockSupportDemo {


    public static void main(String[] args) {
        /**
         * LockSupport是用来创建锁和其他同步类的基本线程阻塞原语
         * 
         * LockSupport是一个线程阻塞工具类，所有的方法都是静态方法，可以让线程在任意位置阻塞，阻塞之后也有对应的唤醒方法，归根结底
         * LockSupport调用的是Unsafe中的native代码
         * 
         * LockSupport提供park()和unpark()方法实现阻塞线程和解除阻塞的过程
         * LockSupport和每个使用他的线程都有一个许可(permit)关联
         * 每个线程都有一个相关的permit，permit最多只有一个，重复调用unpark()也不会积累凭证
         * 
         * 当调用park()方法时：
         *      如果有凭证，则会消耗掉这个凭证，然后正常退出
         *      如果没有凭证，就必须等待凭证可用
         *      
         * 调用unpark()方法时：
         *      他会增加一个凭证，但凭证最多只有一个，无法累计
         * 
         * LockSupport
         *  无锁块要求
         *  LockSupport支持先唤醒后等待
         *  
         *  LockSupport许可证不会累计，最多只有一个
         *  
         *  
         *  面试题：
         *      为什么可以突破 wait/notify的原有调用顺序？
         *          因为unpark()获得了一个凭证，之后在调用park方法时，就可以通过消费这个凭证，故不会阻塞
         *          
         *      为什么唤醒两次后阻塞两次，但最终结果还是会阻塞线程？
         *          应为凭证不可累加，最多只能为1，即调用两次unpark()和调用一次效果一样，而调用两次park()需要消费两个凭证，
         *          凭证不够，不能放行，进而造成阻塞
         */
        
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t  come in");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "\t  被唤醒");
        },"t1");
        t1.start();
        

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName() + "\t  发出通知" );
        },"t2").start();
    }
    
    

    public static void lockAwaitSignal() {
        /**
         * Condition 中的线程等待和唤醒方法，需要先获取锁
         * 一定要先 await()  后 signal()
         */
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t  come in");
                condition.await();
                System.out.println(Thread.currentThread().getName() + "\t  被唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        },"t1").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "\t  发出通知" );
            }finally {
                lock.unlock();
            }
        },"t2").start();
    }


    /**
     * synchronized wait notify 之间的关系
     */
    public static void syncWaitNotify() {
        /**
         * wait() notify() notifyAll() 需要在同步代码块中使用，否则抛出异常   （synchronized）
         * 先使用 wait()  在使用 notify()
         */

        Object objectLock = new Object();

        new Thread(() -> {
            synchronized (objectLock){
                System.out.println(Thread.currentThread().getName() + "\t  come in");
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t  被唤醒");
            }
        },"t1").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            synchronized (objectLock){
                objectLock.notify();
                System.out.println(Thread.currentThread().getName() + "\t  发出通知" );
            }
        },"t2").start();
    }
}
